/*
 * Project: Set Sample
 * Copyright (C) 2013 alf.labs gmail com,
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.alflabs.setsample.presenter;

import com.alflabs.setsample.model.SetBoard;
import com.alflabs.setsample.model.SetCard;
import com.alflabs.setsample.model.SetModel;
import com.alflabs.setsample.model.SetSolution;
import com.alflabs.setsample.stats.SetResult;
import com.alflabs.utils.RSparseBooleanArray;
import com.alflabs.annotations.NonNull;
import com.alflabs.annotations.Null;
import com.alflabs.serial.SerialReader;
import com.alflabs.serial.SerialWriter;

import java.util.Random;
import java.util.Set;
import java.util.TreeSet;

public class SetPresenterImpl {

    private final static String TAG = SetPresenterImpl.class.getSimpleName();

    private final RSparseBooleanArray mSelectedCards = new RSparseBooleanArray(3);

    @NonNull private final SetModel mModel;
    @Null private IAbstractView mView;
    private long mCounter;
    private long mTimeCounterMs;
    private boolean mDisplayAllSolutions;

    public SetPresenterImpl(@Null IAbstractView view, @NonNull SetModel model) {
        mModel = model;
        connectView(view);
    }

    public void connectView(@Null IAbstractView view) {
        mView = view;
        getModel().setAbstractView(view);
    }

    @NonNull
    public SetModel getModel() {
        return mModel;
    }

    /** For unit tests */
    public long getCounter() {
        return mCounter;
    }

    /** Time counter, in milliseconds. */
    public long getTimeCounterMs() {
        return mTimeCounterMs;
    }

    public void incTimeCounterMs(long delta) {
        if (!mModel.isCompleted()) {
            mTimeCounterMs += delta;
        }
    }

    public boolean isDisplayAllSolutions() {
        return mDisplayAllSolutions;
    }

    public void setDisplayAllSolutions(boolean displayAllSolutions) {
        mCounter++;
        mDisplayAllSolutions = displayAllSolutions;
    }

    /** For unit tests. */
    public Set<Integer> getSelectedCards() {
        Set<Integer> set = new TreeSet<Integer>();
        for (int i = 0; i < mSelectedCards.size(); i++) {
            set.add(mSelectedCards.keyAt(i));
        }
        return set;
    }

    /**
     * Persist model's data + presenter (UI state) data to the serial.
     */
    public SerialWriter toSerial() {
        SerialWriter w = new SerialWriter();
        SetModel m = getModel();
        w.addLong("mModel_mSeed", m.getSeed());
        w.addLong("mModel_mCounter", m.getCounter());

        w.addLong("mPresenter_mCounter", mCounter);
        w.addLong("mPresenter_mTimeCounterMs", mTimeCounterMs);
        w.addBool("mDisplayAllSolutions", mDisplayAllSolutions);

        w.addInt("mSelectedCards_0", mSelectedCards.size() <= 0 ? -1 : mSelectedCards.keyAt(0));
        w.addInt("mSelectedCards_1", mSelectedCards.size() <= 1 ? -1 : mSelectedCards.keyAt(1));
        w.addInt("mSelectedCards_2", mSelectedCards.size() <= 2 ? -1 : mSelectedCards.keyAt(2));

        w.addSerial("mModel", m.toSerial());
        return w;
    }

    /**
     * Restore serial if its model seed is different or counter is newer.
     */
    public void fromSerial(@NonNull SerialReader sr) {
        long seed = sr.getLong("mModel_mSeed");
        long modelCounter = sr.getLong("mModel_mCounter");

        SetModel m = getModel();
        boolean modelChanged = false;
        if (seed != m.getSeed() || modelCounter > m.getCounter()) {
            // restore model
            SerialReader msr = sr.getSerial("mModel");
            m.fromSerial(msr);
            modelChanged = true;
        }

        long presCounter = sr.getLong("mPresenter_mCounter");
        long timeCounter = sr.getLong("mPresenter_mTimeCounterMs");
        if (modelChanged || presCounter > mCounter || timeCounter != mTimeCounterMs) {
            // restore presenter
            mCounter = presCounter;
            mTimeCounterMs = timeCounter;
            mDisplayAllSolutions = sr.getBool("mDisplayAllSolutions");
            mSelectedCards.clear();
            int i;
            i = sr.getInt("mSelectedCards_0"); if (i != -1) mSelectedCards.put(i, true);
            i = sr.getInt("mSelectedCards_1"); if (i != -1) mSelectedCards.put(i, true);
            i = sr.getInt("mSelectedCards_2"); if (i != -1) mSelectedCards.put(i, true);
        }
    }

    public void randomizeNewBoard(Random rnd) {
        mCounter++;
        mTimeCounterMs = 0;
        getModel().randomizeNewGame(rnd);
    }

    public void deselectAllCells() {
        mCounter++;
        if (mView != null) {
            int n = mSelectedCards.size();
            mView.deselectCells(
                    n <= 0 ? -1 : mSelectedCards.keyAt(0),
                    n <= 1 ? -1 : mSelectedCards.keyAt(1),
                    n <= 2 ? -1 : mSelectedCards.keyAt(2));
        }
        mSelectedCards.clear();
    }

    public void onCellChanged(int index, boolean checked) {
        if (index < 0 || index > SetBoard.SXY) return;

        mCounter++;
        if (checked) {
            mSelectedCards.put(index, true);
        } else {
            mSelectedCards.delete(index);
        }

        if (mSelectedCards.size() == 3) {
            SetResult completedResult = null;
            int msg = IAbstractView.MSG_SOLUTION_NOT_A_SET;
            SetModel m = getModel();
            SetBoard b = m.getBoard();
            SetCard c1 = b.getCell(mSelectedCards.keyAt(0));
            SetCard c2 = b.getCell(mSelectedCards.keyAt(1));
            SetCard c3 = b.getCell(mSelectedCards.keyAt(2));
            if (SetCard.isSet(c1, c2, c3)) {
                SetSolution s = new SetSolution(c1, c2, c3);
                if (m.addUserSolution(s)) {
                    if (m.isCompleted()) {
                        completedResult = m.getStats().addStat(m.getPossibleSolutions().size(), mTimeCounterMs);
                    } else {
                        msg = IAbstractView.MSG_SOLUTION_NEW_SET_FOUND;
                    }
                } else {
                    msg = IAbstractView.MSG_SOLUTION_ALREADY_FOUND;
                }
            }

            if (mView != null) {
                if (completedResult != null) {
                    mView.onBoardCompleted(completedResult);
                } else {
                    mView.onNewUserSolution(msg);
                }
            }

            // deselect the 3 cells, whether they were a match or not.
            deselectAllCells();
        }
    }
}
